/**
 * 
 */
package RecordManagement;

import PagedFile.BF_Manager;
import PagedFile.Buffer;

/**
 * @author Neil
 *
 */
public class RM_FileScan implements RM_FileScan_Interface {

	boolean open;
	
	/* Imported Information */
	RM_FileHandle rmFileHandle;
	ScanType st;
	
	/* Domestic Information for scanning */
	int pageToBePinned; //A scan handle may have several pinned pages, determined by ScanType.pinHint
	int pinnedPageNum;
	int slotNum;
	int pinnedPageIndex;
	Buffer[] pinnedPages;
	int currentScanPage;

	/*
	 * The Constructure require the client define the precise Scan Type,
	 * which tells the way of scanning.
	 * ScanType attributes see class ScanType.
	 */
	public RM_FileScan(RM_FileHandle fh, ScanType st) {
		if (fh == null || fh.openCnt == 0)
			throw new RM_Abort_Exception("Handle Not Open");
		this.open = true;
		
		this.rmFileHandle = fh;
		this.st = st;
		
		pinnedPageNum = 0;
		currentScanPage = 0;
		slotNum = 0;
		pinnedPageIndex = 1;
		
		pinnedPages = new Buffer[BF_Manager.PF_BUFFER_SIZE];
	}
	
	/* (non-Javadoc)
	 * @see RecordManagement.RM_FileScan_Interface#CloseScan()
	 */
	@Override
	public void CloseScan() {
		if ( !open )
			throw new RM_Abort_Exception("Scan Not Open");
		st = null;
		for (int i=0; i<pinnedPageNum; ++i)
			rmFileHandle.pfm.DisposeBlock(pinnedPages[i]);
		open = false;
	}

	/* (non-Javadoc)
	 * @see RecordManagement.RM_FileScan_Interface#GetNextRec()
	 */
	@Override
	public RM_Record GetNextRec() {
		if (!open)
			throw new RM_Abort_Exception("Scan Not Open");
		RM_Record rec = GetNextRecInMemory();
		if ( rec == null ) {
			FetchPages();
			return GetNextRecInMemory();
		}
		return rec;
	}

	private static FunctionalClass.Buffer_Bit_Operation bbop = new FunctionalClass.Buffer_Bit_Operation();

	private RM_Record GetNextRecInMemory() {
		if (currentScanPage >= pinnedPageNum)
			return null;
		/* Scan records in each of the pinned pages starting from where we
		 * stopped last */
		byte[] recordData=new byte[rmFileHandle.rmFileHead.recordSize];
		while (currentScanPage < pinnedPageNum) {
			while (slotNum < rmFileHandle.rmFileHead.recordPerPage) {
				Buffer buf = pinnedPages[currentScanPage];
				if (bbop.bitTest(buf.contents, RM_Manager.RM_PAGEHEAD_SIZE, slotNum)) {
					buf.contents.position(rmFileHandle.rmFileHead.firstRecordOffset+rmFileHandle.rmFileHead.recordSize*slotNum);
					buf.contents.get(recordData);
					if ( !CheckCondition(recordData) ) {
						++slotNum;
						continue;
					}
					RM_Record ret = new RM_Record(new RID(buf.block.pageNum, slotNum), recordData);
					++slotNum;
					return ret;
				}
				++slotNum;
			}
			slotNum=0;
			rmFileHandle.pfm.DisposeBlock(pinnedPages[currentScanPage++]);
//			RM_BufferHandle rmBufferHandle = new RM_BufferHandle(pinnedPages[currentScanPage], rmFileHandle.rmFileHead);
//			while (slotNum < rmFileHandle.rmFileHead.recordPerPage) {
//				if (rmBufferHandle.SlotTest(slotNum)) {
//					recordData = rmBufferHandle.GetRecordData(slotNum);
//					if ( !CheckCondition(recordData) ) {
//						++slotNum;
//						continue;
//					}
//					RM_Record ret = new RM_Record(new RID(rmBufferHandle.pageNum, slotNum), recordData);
//					++slotNum;
//					return ret;
//				}
//				++slotNum;
//			}
//			slotNum = 0;
//			rmFileHandle.pfm.DisposeBlock(pinnedPages[currentScanPage]);
//			++currentScanPage;
		}
		return null;
	}
	
	private boolean CheckCondition(byte[] record) {
		if (st.compOp == ScanType.OP_NO)
			return true;
		
		Integer attrInt=0, compInt=0;
		String attrString="", compString="";
		switch(st.attrType) {
		case (ScanType.TYPE_INT):
			attrInt = new Integer(new String(record, st.attrOffset, st.attrLength));
			compInt = new Integer(new String(st.compValue));
			break;
		case ScanType.TYPE_STRING:
			attrString = new String(record, st.attrOffset, st.attrLength);
			compString = new String(st.compValue);
		}
		
		switch(st.compOp) {
		case ScanType.OP_EQ: 
			if (st.attrType == ScanType.TYPE_INT)
				return attrInt == compInt;
			return attrString.compareTo(compString)==0;
		case ScanType.OP_GE:
			if (st.attrType == ScanType.TYPE_INT)
				return attrInt >= compInt;
			return attrString.compareTo(compString)>=0;
		case ScanType.OP_GT:
			if (st.attrType == ScanType.TYPE_INT)
				return attrInt > compInt;
			return attrString.compareTo(compString)>0;
		case ScanType.OP_LE:
			if (st.attrType == ScanType.TYPE_INT)
				return attrInt <= compInt;
			return attrString.compareTo(compString)<=0;
		case ScanType.OP_LT:
			if (st.attrType == ScanType.TYPE_INT)
				return attrInt < compInt;
			return attrString.compareTo(compString)<0;
		case ScanType.OP_NE:
			if (st.attrType == ScanType.TYPE_INT)
				return attrInt != compInt;
			return attrString.compareTo(compString)!=0;
		}
		throw new RM_Abort_Exception("Fatal");
	}
	
	private void FetchPages() {

/*		if (pinnedPageNum>0) {
			for (int i=0; i<pinnedPageNum; ++i)
				rmFileHandle.pfm.DisposeBlock(pinnedPages[i]);
		}*/
		
		pinnedPageNum = 0;
		currentScanPage = 0;
		slotNum = 0;
		switch(st.pinHint){
		case ScanType.PIN_NORMAL:
			pageToBePinned = 1;
			break;
		case ScanType.PIN_EFFICIENT:
			pageToBePinned = Math.max(rmFileHandle.pfm.bfm.available()-2,1);
			break;
		}
		
		for (int i=0; i<pageToBePinned; ++i) {
			pinnedPages[i] = rmFileHandle.pfFileHandle.GetNextPage(pinnedPageIndex);
			if (pinnedPages[i]==null)
				break;
			pinnedPageIndex = pinnedPages[i].block.pageNum;
			++pinnedPageNum;
		}
/*		if (pinnedPageNum == 0)
			throw new RM_Abort_Exception();
*/	}
}
